﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Reflection;

namespace Eventable
{
    /// <summary>
    /// Событийный класс
    /// </summary>
    /// <typeparam name="T">Класс владельца экземпляром провайдера</typeparam>
    public class EventProvider<T> : IEventProvider, IEventHandleObject, IDisposable
        where T : class
    {
        private static readonly HashSet<string> defaultEvents = new HashSet<string>();
        private static readonly Dictionary<string, MethodInfo> defaultHandles = new Dictionary<string, MethodInfo>();

        static EventProvider()
        {
            Type t = typeof(T);
            foreach (var item in t.GetAttributes<RegisterEventsAttribute>(true)
                .SelectMany(s => s.NameEvents))
                defaultEvents.Add(item);

            EventHandlerAttribute eha;
            ParameterInfo[] pi;
            foreach (var item in t
                .GetMethodsWithAttribute<EventHandlerAttribute>(true)
                .Where(s =>
                    (pi = s.GetParameters()).Length == 3
                    && pi[0].ParameterType == typeof(object)
                    && pi[1].ParameterType == typeof(string)
                    && pi[2].ParameterType == typeof(object[])
                    && !s.IsStatic))
            {
                eha = item.GetAttribute<EventHandlerAttribute>(true);
                if (!eha.HandlerName.IsEmpty())
                    defaultHandles.Add(eha.HandlerName, item);
                else
                    defaultHandles.Add(item.Name, item);
            }
        }


        /// <summary>
        /// Заглушка для событий
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        protected static bool NullTrueEvent(object sender, string evnt, params object[] args)
        {
            return true;
        }
        /// <summary>
        /// Ссылки на методы обработчики событий
        /// </summary>
        protected Dictionary<string, EventHandlerParams> DictEventHands = new Dictionary<string, EventHandlerParams>();
        /// <summary>
        /// Внутренние методы обработчики событий
        /// </summary>
        protected Dictionary<string, EventHandlerParams> DictHandlers = new Dictionary<string, EventHandlerParams>();
        /// <summary>
        /// 
        /// </summary>
        //protected Dictionary<WeakReference,string, EventHandlerParams> DictHandles = new Dictionary<string, EventHandlerParams>();

        /// <summary>
        /// Зарегистрировать события.
        /// </summary>
        /// <param name="nameEvents"></param>
        public void RegisterEvent(params string[] nameEvents)
        {
            if (nameEvents != null)
                foreach (var item in nameEvents)
                    if (!DictEventHands.ContainsKey(item))
                        DictEventHands.Add(item, null);
        }

        /// <summary>
        /// Зарегистрировать события.
        /// </summary>
        /// <param name="nameEvents"></param>
        public void RegisterEvents(IEnumerable<string> nameEvents)
        {
            if (nameEvents != null)
                foreach (var item in nameEvents)
                    if (!DictEventHands.ContainsKey(item))
                        DictEventHands.Add(item, null);
        }

        /// <summary>
        /// Зарегистрировать метод обработчик события
        /// </summary>
        /// <param name="nameHandle">Наименование метода</param>
        /// <param name="Handle"></param>
        public void RegisterHandle(string nameHandle, EventHandlerParams Handle)
        {
            if (!DictHandlers.ContainsKey(nameHandle))
                DictHandlers.Add(nameHandle, Handle);
        }

        /// <summary>
        /// Оповестить о событии
        /// </summary>
        /// <param name="nameEvent">Идентификатор события</param>
        /// <param name="args">Параметры события</param>
        public bool NotifyEvent(string nameEvent, params object[] args)
        {
            if (DictEventHands.ContainsKey(nameEvent))
                return (DictEventHands[nameEvent] ?? NullTrueEvent)(Owner, nameEvent, args);
            else
                Debug.WriteLine("Не найдено событие: " + nameEvent);
            return true;
        }

        /// <summary>
        /// Установка прослушивания события
        /// </summary>
        /// <param name="evidence">Мандат (разрешение) на прослушивание</param>
        /// <param name="nameEvent">Идентификатор события</param>
        /// <param name="handler">Ссылка на метод обработчик</param>
        public bool ListenEvent(string nameEvent, EventHandlerParams handler)
        {
            if (DictEventHands.ContainsKey(nameEvent))
                AttachHandle(nameEvent, handler);
            else
                return false;
            return true;
        }

        /// <summary>
        /// Отказ от прослушивания события
        /// </summary>
        /// <param name="nameEvent">Идентификатор события</param>
        /// <param name="handler">Ссылка на метод обработчик</param>
        public bool UnlistenEvent(string nameEvent, EventHandlerParams handler)
        {
            EventHandlerParams hnd;
            if (DictEventHands.TryGetValue(nameEvent, out hnd)
                && hnd.GetInvocationList().Contains(handler))
                hnd -= handler;
            else
                return false;
            return true;
        }
        public string GetNameFromHandle(EventHandlerParams eh)
        {
            KeyValuePair<string, EventHandlerParams> result;
            if (eh != null && DictHandlers.ContainsValue(eh))
            {
                result = DictHandlers.FirstOrDefault(s => s.Value == eh);
                if (result.Value != eh)
                    return result.Key;
            }
            return "";
        }
        public EventHandlerParams GetHandleFromName(string nameHandle)
        {
            if (DictHandlers.ContainsKey(nameHandle))
                return DictHandlers[nameHandle];
            else
                return null;
        }
        private void RegisterEventAndAttach(string nameEvent, EventHandlerParams handler)
        {
            if (!DictEventHands.ContainsKey(nameEvent))
                DictEventHands.Add(nameEvent, handler);
            else
                AttachHandle(nameEvent, handler);
        }
        private void AttachHandle(string nameEvent, EventHandlerParams handler)
        {
            DictEventHands[nameEvent] += handler;
        }
        public void DettachEvents()
        {
            DictEventHands.Clear();
        }
        private void Init(T owner, bool autoBind, params string[] events)
        {
            Owner = owner;
            if (autoBind)
                AutoBinding();
            RegisterEvent(events);
        }

        private void AutoBinding()
        {
            RegisterEvents(defaultEvents);
            foreach (var item in defaultHandles)
            {
                DictHandlers.Add(item.Key,
                    (EventHandlerParams)Delegate.CreateDelegate(typeof(EventHandlerParams), Owner, item.Value, true));
            }
        }

        public EventProvider()
            : this(default(T), true)
        {

        }
        public EventProvider(T owner)
            : this(owner, true)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="owner">Владелец</param>
        /// <param name="events">Список событий</param>
        public EventProvider(T owner, bool autoBind, params string[] events)
        {
            Init(owner, autoBind, events);
        }

        private T _Owner;
        protected T Owner
        {
            get
            {
                return _Owner;
            }
            set
            {
                _Owner = value;
            }
        }

        public void Dispose()
        {           
        }
    } // class EventableObject    
    
}
